package mqtt.utils;

import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import java.util.LinkedHashSet;
import java.util.Set;

/**
 * 扫描指定的包
 * <p>
 * {@link org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider}
 * 基于spring框架扫描包工具类下自定义扫描包
 *
 * @Author: Lamb
 * @Date: 2022/9/23 16:00
 */
public class ClassPathScanningProvider implements EnvironmentCapable, ResourceLoaderAware {


    private String resourcePattern;

    @Nullable
    private Environment environment;

    @Nullable
    private ResourcePatternResolver resourcePatternResolver;

    @Nullable
    private MetadataReaderFactory metadataReaderFactory;

    private CandidateFilter candidateFilter;

    public ClassPathScanningProvider() {
        this.resourcePattern = "**/*.class";
        this.environment = new StandardEnvironment();
        this.setResourceLoader(null);
    }

    public final Environment getEnvironment() {
        if (environment == null) {
            environment = new StandardEnvironment();
        }
        return environment;
    }

    public void setResourceLoader(@Nullable ResourceLoader resourceLoader) {
        if (resourceLoader instanceof ResourcePatternResolver) {
            resourcePatternResolver = (ResourcePatternResolver) resourceLoader;
        } else {
            resourcePatternResolver = resourceLoader != null ? new PathMatchingResourcePatternResolver(resourceLoader) : new PathMatchingResourcePatternResolver();
        }

        metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    }

    /**
     * 根据一个或多个包名获取该包名下及其子包下的所有Class
     *
     * @param basePackages 一个或多个包名
     * @return
     * @throws Exception
     */
    public Set<Class> findClassSet(String... basePackages) throws Exception {
        Set<Class> classSet = new LinkedHashSet();
        for (String basePackage : basePackages) {
            String packageSearchPath = "classpath*:" + resolveBasePackage(basePackage) + '/' + resourcePattern;
            Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
            int len = resources.length;
            for (int i = 0; i < len; ++i) {
                Resource resource = resources[i];
                if (resource.isReadable()) {
                    MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                    String className = metadataReader.getAnnotationMetadata().getClassName();
                    Class<?> c = Class.forName(className);
                    if (candidateFilter != null) {
                        if (candidateFilter.isCandidate(className, c)) {
                            classSet.add(c);
                        }
                    } else {
                        classSet.add(c);
                    }
                }
            }
        }
        return classSet;
    }

    private String resolveBasePackage(String basePackage) {
        String className = getEnvironment().resolveRequiredPlaceholders(basePackage);
        Assert.notNull(className, "Class name must not be null");
        return className.replace('.', '/');
    }

    /**
     * 添加候选人筛选器
     *
     * @param candidateFilter
     */
    public void addCandidateFilter(CandidateFilter candidateFilter) {
        this.candidateFilter = candidateFilter;
    }

}
